/*******************************************************************************
 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.report.internal.html.table;

import java.io.IOException;
import java.util.*;

import org.jacoco.core.analysis.ICoverageNode;
import org.jacoco.core.internal.diff.ClassInfoDto;
import org.jacoco.core.internal.diff.MethodInfoDto;
import org.jacoco.core.tools.ExecFileLoader;
import org.jacoco.report.internal.ReportOutputFolder;
import org.jacoco.report.internal.html.HTMLElement;
import org.jacoco.report.internal.html.resources.Resources;
import org.jacoco.report.internal.html.resources.Styles;

/**
 * Renderer for a table of {@link ITableItem}s.
 */
public class Table {

    private final List<Column> columns;

    private Comparator<ITableItem> defaultComparator;

    /**
     * Create a new table without any columns yet.
     */
    public Table() {
        this.columns = new ArrayList<Table.Column>();
    }

    /**
     * Adds a new column with the given properties to the table.
     *
     * @param header         column header caption
     * @param style          optional CSS style class name for the td-Elements of this
     *                       column
     * @param renderer       callback for column rendering
     * @param defaultSorting If <code>true</code>, this column is the default sorting
     *                       column. Only one column can be selected for default sorting.
     */
    public void add(final String header, final String style,
                    final IColumnRenderer renderer, final boolean defaultSorting) {
        columns.add(new Column(columns.size(), header, style, renderer,
                defaultSorting));
        if (defaultSorting) {
            if (defaultComparator != null) {
                throw new IllegalStateException(
                        "Default sorting only allowed for one column.");
            }
            this.defaultComparator = renderer.getComparator();
        }
    }

    /**
     * Renders a table for the given icon
     *
     * @param parent    parent element in which the table is created
     * @param items     items that will make the table rows
     * @param total     the summary of all coverage data items in the table static
     *                  resources that might be referenced
     * @param resources static resources that might be referenced
     * @param base      base folder of the table
     * @throws IOException in case of IO problems with the element output
     */
    public void render(final HTMLElement parent,
                       final List<? extends ITableItem> items, final ICoverageNode total,
                       final Resources resources, final ReportOutputFolder base)
            throws IOException {
        final List<? extends ITableItem> sortedItems = sort(items);
        final HTMLElement table = parent.table(Styles.COVERAGETABLE);
        table.attr("id", "coveragetable");
        header(table, sortedItems, total);
        footer(table, total, resources, base);
        body(table, sortedItems, resources, base, total);
    }

    private void header(final HTMLElement table,
                        final List<? extends ITableItem> items, final ICoverageNode total)
            throws IOException {
        final HTMLElement tr = table.thead().tr();
        for (final Column c : columns) {
            c.init(tr, items, total);
        }
    }

    private void footer(final HTMLElement table, final ICoverageNode total,
                        final Resources resources, final ReportOutputFolder base)
            throws IOException {
        final HTMLElement tr = table.tfoot().tr();
        for (final Column c : columns) {
            c.footer(tr, total, resources, base);
        }
    }

    private void body(final HTMLElement table,
                      final List<? extends ITableItem> items, final Resources resources,
                      final ReportOutputFolder base, final ICoverageNode total)
            throws IOException {
        final HTMLElement tbody = table.tbody();
        int idx = 0;
        for (final ITableItem item : items) {
            final HTMLElement tr = tbody.tr();
            for (final Column c : columns) {
                c.body(tr, idx, item, resources, base, total);
            }
            idx++;
        }
    }

    private List<? extends ITableItem> sort(
            final List<? extends ITableItem> items) {
        if (defaultComparator != null) {
            final List<ITableItem> result = new ArrayList<ITableItem>(items);
            Collections.sort(result, defaultComparator);
            return result;
        }
        return items;
    }

    private static class Column {

        private final char idprefix;
        private final String header;
        private final IColumnRenderer renderer;
        private final SortIndex<ITableItem> index;
        private final String style, headerStyle;

        private boolean visible;

        Column(final int idx, final String header, final String style,
               final IColumnRenderer renderer, final boolean defaultSorting) {
            this.idprefix = (char) ('a' + idx);
            this.header = header;
            this.renderer = renderer;
            index = new SortIndex<ITableItem>(renderer.getComparator());
            this.style = style;
            this.headerStyle = Styles.combine(
                    defaultSorting ? Styles.DOWN : null, Styles.SORTABLE,
                    style);
        }

        void init(final HTMLElement tr, final List<? extends ITableItem> items,
                  final ICoverageNode total) throws IOException {
            visible = renderer.init(items, total);
            if (visible) {
                index.init(items);
                final HTMLElement td = tr.td(headerStyle);
                td.attr("id", String.valueOf(idprefix));
                td.attr("onclick", "toggleSort(this)");
                td.text(header);
            }
        }

        void footer(final HTMLElement tr, final ICoverageNode total,
                    final Resources resources, final ReportOutputFolder base)
                throws IOException {
            if (visible) {
                renderer.footer(tr.td(style), total, resources, base);
            }
        }

        void body(final HTMLElement tr, final int idx, final ITableItem item,
                  final Resources resources, final ReportOutputFolder base,
                  final ICoverageNode total) throws IOException {
            if (visible) {
                final HTMLElement td = tr.td(style);
                td.attr("id",
                        idprefix + String.valueOf(index.getPosition(idx)));
                if (this.header.equals("修改人")
                        || this.header.equals("提交信息")) {
                    if (ExecFileLoader.classInfo.get() != null) {
                        Map<String, Map<String, List<MethodInfoDto>>> classInfoDtos = ExecFileLoader.classInfo
                                .get();
                        if (classInfoDtos.containsKey(total.getName())) {
                            Map<String, List<MethodInfoDto>> method = classInfoDtos
                                    .get(total.getName());
                            if (method.containsKey(item.getNode().getName())) {
                                String text = this.header.equals("修改人")
                                        ? method.get(item.getNode().getName())
                                        .get(0).getAuthor()
                                        : method.get(item.getNode().getName())
                                        .get(0).getCommitMessage();
                                text = text == null ? "未知，请联系平台管理员！" : text;
                                td.text(text);
                            }
                        }
                    } else {
                        td.text("未知，请联系平台管理员！");
                    }
                } else {
                    renderer.item(td, item, resources, base);
                }
            }
        }

    }

}
